﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;
using System.Reflection;

namespace RuleSet {
    public static class RuleExtension {

        /// <summary>
        /// Extension method to apply RuleSet to IQueryable
        /// </summary>
        /// <typeparam name="T">Entity Type</typeparam>
        /// <param name="data">entities IQueryable</param>
        /// <param name="rule">PredicateRule, Conjunction or Disjunction</param>
        /// <returns>IQueryable of entity type</returns>
        public static IQueryable<T> ExecuteRule<T>(this  IQueryable<T> data, BaseRule rule) where T : class {
            ParameterExpression entityParam = Expression.Parameter(typeof(T), "entity");
            return rule.ToExpression<T>(entityParam).ToIQueryable<T>(data, entityParam);
        }

        public static Expression ToExpression<T>(this BaseRule rule, ParameterExpression entity) where T : class {
            if (rule is PredicateRule<T>) {
                PredicateRule<T> pr = (PredicateRule<T>)rule;
                return pr.ConvertPredicate<T>(entity);
            } else if (rule is Conjunction<T>){
                Conjunction<T> conjunction = (Conjunction<T>)rule;
                IList<Expression> exps = new List<Expression>();
                foreach (var x in conjunction.ChildRuleCollection) {
                    exps.Add(x.ToExpression<T>(entity));
                }
                return exps.AndExpressionFromExpressionList<T>();
            } else if (rule is Disjunction<T>) {
                Disjunction<T> disjunction = (Disjunction<T>)rule;
                IList<Expression> exps = new List<Expression>();
                foreach (var x in disjunction.ChildRuleCollection) {
                    exps.Add(x.ToExpression<T>(entity));
                }
                return exps.OrExpressionFromExpressionList<T>();
            } else {
                throw new NotImplementedException("Only PredicateRule, Conjunction, Disjunction has been implemented");
            }
        }

        public static Expression ConvertPredicate<T>(this PredicateRule<T> rule, ParameterExpression entity) where T : class {
            Type classType = typeof(T);
            PropertyInfo pi = classType.GetProperty(rule.LHS);

            if (rule.Operator == Operators.Contains &&
                classType.GetProperty(rule.LHS).PropertyType != typeof(string)) {
                    throw new NotSupportedException("Operators.Contains can only operate on string property.");
            }

            Expression lhs = Expression.Property(entity, classType.GetProperty(rule.LHS));
            ConstantExpression rhs = Expression.Constant(rule.RHS, pi.PropertyType);
            switch (rule.Operator) {
                case Operators.Eq:
                    // TODO: Fix case sensitive issues for string
                    return Expression.Equal(lhs, rhs);

                case Operators.Gt:
                    return Expression.GreaterThan(lhs, rhs);

                case Operators.Lt:
                    return Expression.LessThan(lhs, rhs);

                case Operators.GtEq:
                    return Expression.GreaterThanOrEqual(lhs, rhs);

                case Operators.LtEq:
                    return Expression.LessThanOrEqual(lhs, rhs);

                case Operators.NotEq:
                    // TODO: Fix case sensitive issues for string
                    return Expression.NotEqual(lhs, rhs);

                case Operators.Contains:
                    // TODO: Determine whether it is a Linq2SQL or Linq2Object
                    // TODO: Linq2SQL uses SqlMethods.Like
                    // TODO: Linq2Object uses String.Contains
                    // TODO: Fix case sensitive issues
                    return Expression.Call(
                        lhs,
                        typeof(string).GetMethod("Contains"),
                        rhs);
                    //throw new NotImplementedException("\"contains\" for SQL not yet implemented.");
                    
                default:
                    throw new NotSupportedException("Operator not support:" + rule.Operator.ToString());
            }
        }

        public static Expression AndExpressionFromExpressionList<T>(this IList<Expression> exps) where T : class {
            Expression expressionAnd = null;
            if (exps.Count == 1) {
                expressionAnd = exps[0];
            } else {
                expressionAnd = exps[0];
                for (int i = 1; i < exps.Count; i++) {
                    expressionAnd = Expression.AndAlso(expressionAnd, exps[i]);
                }
            }
            return expressionAnd;
        }

        public static Expression OrExpressionFromExpressionList<T>(this IList<Expression> exps) where T : class {
            Expression expressionOr = null;
            if (exps.Count == 1) {
                expressionOr = exps[0];
            } else {
                expressionOr = exps[0];
                for (int i = 1; i < exps.Count; i++) {
                    expressionOr = Expression.OrElse(expressionOr, exps[i]);
                }
            }
            return expressionOr;
        }

        public static IQueryable<T> ToIQueryable<T>(this Expression expression, IQueryable<T> data, ParameterExpression entity) where T : class {
            MethodCallExpression callexp = Expression.Call(
                    typeof(Queryable),
                    "Where",
                    new Type[] { data.ElementType },
                    data.Expression,
                    Expression.Lambda<Func<T, bool>>(expression, new ParameterExpression[] { entity })
                    );
            return data.Provider.CreateQuery<T>(callexp);
        }
    }
}